package demo.impl.ethernet;

import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.pcap4j.core.BpfProgram.BpfCompileMode;
import org.pcap4j.core.PcapHandle;
import org.pcap4j.core.PcapHandle.Builder;
import org.pcap4j.core.PcapNetworkInterface;
import org.pcap4j.core.PcapNetworkInterface.PromiscuousMode;
import org.pcap4j.packet.EthernetPacket;
import org.pcap4j.packet.UnknownPacket;
import org.pcap4j.packet.namednumber.EtherType;
import org.pcap4j.util.LinkLayerAddress;
import org.pcap4j.util.MacAddress;
import org.slf4j.LoggerFactory;

import demo.api.EthernetService;
import demo.exception.NotOpenException;
import demo.impl.AbstractProtocolService;
import demo.util.AssertUtils;
import demo.util.NamedThreadFactory;

/**
 * 以太网服务
 * 
 * @author wangbin
 * @date 2017年8月4日
 */
public class EthernetServiceImpl extends AbstractProtocolService<EthernetPacket> implements EthernetService {

	/** 网络接口 */
	private PcapNetworkInterface nif;

	/** 网卡的Mac地址 */
	private MacAddress macAddress;

	/** pcap句柄 */
	private PcapHandle pcapHandle;

	public EthernetServiceImpl(PcapNetworkInterface nif) {
		super(null);
		open(nif);
	}

	private void open(PcapNetworkInterface nif) {
		AssertUtils.notNull(nif, "参数网络接口不能为空！");

		Builder builder = new PcapHandle.Builder(nif.getName());
		builder.snaplen(65535); // 捕获的最大包长度
		builder.promiscuousMode(PromiscuousMode.NONPROMISCUOUS);// 非混杂模式
		builder.timeoutMillis(1);// get、dispatch的超时时间
		builder.bufferSize(16 * 1024);// 用户缓冲区的大小，NPF收到的包达到此数值才一次性将包从内核缓冲区复制到用户缓冲区？？

		try {
			pcapHandle = builder.build();
			LoggerFactory.getLogger(getClass()).info("打开网卡处理器pcapHandle：" + nif);

			this.nif = nif;

			// 开始主循环
			startMainLoop();
		} catch (Exception e) {
			close();// 打开过程如果异常，需要清理资源
			throw new RuntimeException("打开网络接口失败！" + e.getMessage(), e);
		}
	}

	@Override
	public void unbind() {
		super.unbind();

		nif = null;
		macAddress = null;

		if (pcapHandle != null) {
			try {
				pcapHandle.breakLoop();
				LoggerFactory.getLogger(getClass()).info("关闭网卡处理器pcapHandle");
				pcapHandle.close();
			} catch (Exception e) {
				LoggerFactory.getLogger(getClass()).warn("pcapHandle关闭失败：" + e.getMessage(), e);
			}
			pcapHandle = null;
		}
	}

	@Override
	public void close() {
		unbind(); // 对于此处，close和unbind是相同的含义
	}

	@Override
	protected Number getProtocolType() {
		throw new RuntimeException("在EthernetService上不支持调用getProtocolType()");
	}

	@Override
	protected Number getHighLevelProtocolType(EthernetPacket packet) {
		return packet.getHeader().getType().value();
	}

	private void startMainLoop() {
		NamedThreadFactory factory = new NamedThreadFactory("MainLoopThread", true);
		ExecutorService executor = Executors.newSingleThreadExecutor(factory);
		LoggerFactory.getLogger(getClass()).info("开启主循环.");
		executor.submit(new Runnable() {

			@Override
			public void run() {
				NamedThreadFactory factory = new NamedThreadFactory("ListenerThread", true);
				ExecutorService executor = Executors.newCachedThreadPool(factory);// 要不要限制最大线程数？
				try {
					pcapHandle.loop(-1, EthernetServiceImpl.this, executor);// 每一个新包到来都再一个新线程处理
				} catch (org.pcap4j.core.NotOpenException e) {
					// 当PcapHandler关闭时，触发此异常
					LoggerFactory.getLogger(getClass()).info("pcapHandle已关闭！msg=" + e.getMessage());
				} catch (InterruptedException e) {
					// breakLoop时触发此异常
					LoggerFactory.getLogger(getClass()).info("pcapHandle主循环已关闭！InterruptedException.");
				} catch (Exception e) {
					EthernetServiceImpl.this.close();// 如果底层监听器循环已不可用，那么后续将收不到任何包，所以关闭所有资源
					LoggerFactory.getLogger(getClass()).warn("pcapHandle底层监听器循环异常，关闭pcapHandle：" + e.getMessage(), e);
				} finally {
					executor.shutdown();// 不管是主动调用PcapHandler.close()，还是因错关闭，这儿都关闭线程池
				}
			}
		});
		executor.shutdown();// 只执行这一个任务
	}

	@Override
	protected void finalize() throws Throwable {
		close();
	}

	@Override
	public boolean isOpen() {
		return isBound();
	}

	@Override
	public PcapNetworkInterface getNetworkInterface() {
		return nif;
	}

	@Override
	public void sendPacket(MacAddress mac, EtherType type, byte[] data) {
		AssertUtils.notNull(mac, "参数mac不能为空！");
		AssertUtils.notNull(type, "参数type不能为空！");
		AssertUtils.notNull(data, "参数data不能为空！");

		if (data.length > EtherType.IEEE802_3_MAX_LENGTH) {
			throw new RuntimeException("无法发送Packet，以太网包最大长度1500！实际长度：" + data.length);
		}

		if (!isOpen()) {
			throw new NotOpenException("无法发送Packet，服务已关闭！targetmac=" + mac + ", type=" + type);
		}

		try {
			EthernetPacket.Builder builder = new EthernetPacket.Builder();
			builder.dstAddr(mac).srcAddr(getMacAddress()).type(type);
			builder.payloadBuilder(new UnknownPacket.Builder().rawData(data)).paddingAtBuild(true);
			EthernetPacket packet = builder.build();
			pcapHandle.sendPacket(packet);
		} catch (Exception e) {
			throw new RuntimeException("发送失败！targetmac=" + mac + ", type=" + type + ", msg=" + e.getMessage(), e);
		}
	}

	@Override
	public void setFilter(String bpfExpression) {
		AssertUtils.notNull(bpfExpression, "参数bpfExpression不能为空！");

		if (!isOpen()) {
			throw new NotOpenException("无法设置过滤器，服务已关闭！bpfExpression=" + bpfExpression);
		}

		try {
			pcapHandle.setFilter(bpfExpression, BpfCompileMode.OPTIMIZE);
		} catch (Exception e) {
			throw new RuntimeException("无法设置过滤器！bpfExpression=" + bpfExpression + ", 原因：" + e.getMessage(), e);
		}
	}

	/**
	 * 获取网卡物理地址
	 * 
	 * @return
	 */
	private MacAddress getMacAddress() {
		if (macAddress == null) {
			if (nif == null) {
				throw new RuntimeException("无法获取网卡地址！网卡尚未绑定当当前协议！" + this);
			}
			List<LinkLayerAddress> addresses = nif.getLinkLayerAddresses();
			if (addresses.size() > 1) {
				LoggerFactory.getLogger(getClass()).info("指定网卡存在多个MAC地址，取第一个MAC地址作为发送以太网帧源地址：" + addresses);
			}
			macAddress = (MacAddress) addresses.iterator().next();
		}
		return macAddress;
	}
}
